﻿#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
// int* p=100；  在指针变量会把这个100当成地址
//int main()
//{
//	int num = 100;
//	num = 200;   //变量 可以直接这样修改 如果我不想让num可以修改 我们直接在num前面加一个const即可
//	printf("%d\n", num);
//	return 0;
//}

//int main()
//{
//	const int num = 100; // 具有了常属性  const修饰变量的时候 叫做常变量
//	// 这个被修饰的变量本质上还是变量，只是不能被修改
//	num = 2;   // 编译的时候会报错  表达式必须是可修改的左值  左值指定const对象  
//	printf("%d\n", num);
//	return 0;
//}

//int main()
//{
//	int arr[10] = { 0 };
//	const int n = 10;
//	int arr[n];  // 会报错 表达式必须含有常量值  c99之前不支持变长数组，数组大小是需要常量，常量表达式来指定，不能是变量
//	// 我们可以发现加上const修饰还是会报错   
//	return 0;
//}

//int main()
//{
//	const int n = 10;
//	//n = 20;  不能直接修改
//	int* p = &n;
//	*p = 200;  // 这样就能修改了  
//	printf("%d", n);
//	return 0;
//}

// 但是我们不想让这样就能修改被const修饰的变量

//变量是可以修改的，如果把变量的地址交给⼀个指针变量，通过指针变量的也可以修改这个变量。
//但是如果我们希望⼀个变量加上⼀些限制，不能被修改，怎么做呢？这就是const的作⽤。
//#include <stdio.h>
//int main()
//{
//	int m = 0;
//	m = 20;//m是可以修改的
//	const int n = 0;
//	n = 20;//n是不能被修改的
//	return 0;
//上述代码中n是不能被修改的，其实n本质是变量，只不过被const修饰后，在语法上加了限制，只要我
//们在代码中对n就⾏修改，就不符合语法规则，就报错，致使没法直接修改n。

//但是如果我们绕过n，使⽤n的地址，去修改n就能做到了，虽然这样做是在打破语法规则。

//我们可以看到这⾥⼀个确实修改了，但是我们还是要思考⼀下，为什么n要被const修饰呢？就是为了
//不能被修改，如果p拿到n的地址就能修改n，这样就打破了const的限制，这是不合理的，所以应该让
//p拿到n的地址也不能修改n，那接下来怎么做呢？


//int main()
//{
//	int n = 10;
//	int m = 100;
//
//	int* p = &n;
//	*p = 20; // 改变了n的内容
//
//	p = &m;  // 改变了变量p的内容
//	// 指针变量p 就指向了新地址m   关于指针p有3个相关的值 
//	// 1.p，p里边放着一个地址    2.*p，p指向的那个对象  3.&p，表示的是p变量的地址
//	return 0;
//}

//int main()
//{
//	int n = 10;
//	int m = 100;
//
//	int const * p = &n;
//	*p = 20; // 这个时候这个就会报错
//
//	p = &m;  // 改变了变量p的内容
//	// 指针变量p 就指向了新地址m   关于指针p有3个相关的值 
//	// 1.p，p里边放着一个地址    2.*p，p指向的那个对象  3.&p，表示的是p变量的地址
//	return 0;
//}
//int main()
//{
//	int n = 10;
//	int m = 100;
//
//	int * const p = &n;
//	*p = 20; // 改变了n的内容
//
//	p = &m;  // 这个时候这个就会报错
//	// 指针变量p 就指向了新地址m   关于指针p有3个相关的值 
//	// 1.p，p里边放着一个地址    2.*p，p指向的那个对象  3.&p，表示的是p变量的地址
//	return 0;
//}


//int main()
//{
//	int n = 10;
//	int m = 100;
//
//	int const * const p = &n;  //这样就既能修饰指针指向的内容 也可以修饰指针变量本身
//	*p = 20; //err
//
//	p = &m;  // err
//	// 指针变量p 就指向了新地址m   关于指针p有3个相关的值 
//	// 1.p，p里边放着一个地址    2.*p，p指向的那个对象  3.&p，表示的是p变量的地址
//	return 0;
//}


//结论：const修饰指针变量的时候
//• const如果放在* 的左边，修饰的是指针指向的内容，保证指针指向的内容不能通过指针来改变。
//但是指针变量本⾝的内容可变。
//• const如果放在* 的右边，修饰的是指针变量本⾝，保证了指针变量的内容不能修改，但是指针指
//向的内容，可以通过指针改变。

//指针的基本运算有三种，分别是：
//• 指针 + -整数
//• 指针 - 指针
//• 指针的关系运算

//因为数组在内存中是连续存放的，只要知道第⼀个元素的地址，顺藤摸⽠就能找到后⾯的所有元素。
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	//  下标        0 1 2 3 4 5 6 7 8 9
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", arr[i]);  //使用数组下标打印数组
//	}
//	return 0;
//}

//// 利用指针打印数组  指针类型决定了指针+1的步长 决定了指针解引用的权限   数组在内存中是连续存放的
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	int* p = &arr[0];  // 得到数组第一个元素地址
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ",*p);
//		p++; // 每次这个指针变量+1 是int类型的就会正好跳过四个字节 也就是一个整数
//	}
//	return 0;
//}
//
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	int* p = &arr[0];  // 得到数组第一个元素地址
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", *(p+i)); // 这样也可以打印数组    int*p  p+i是跳过i*sizeof（int）个字节
//	}
//	return 0;
//}

// 倒着打印数组
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	int* p = &arr[sz-1];  // 得到数组第一个元素地址
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", *p); // 这样也可以打印数组    int*p  p+i是跳过i*sizeof（int）个字节
//		p--;
//	}
//	return 0;
//}

//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	int* p = &arr[sz - 1];  // 得到数组第一个元素地址
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", *p); // 这样也可以打印数组    int*p  p+i是跳过i*sizeof（int）个字节
//
//	}
//	return 0;
//}

// 指针 - 指针的绝对值是指针和指针之间元素的个数   计算的前提条件是两个指针指向的是同一个空间
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	printf("%d\n", &arr[9] - &arr[0]);
//	// 9 因为是一块空间 &arr[0]是这小空间的起始  &arr[9]是另一个小空间的起始
//	// 所以他们之间的元素个数是9个而不是10个
//	printf("%d\n", &arr[0] - &arr[9]); // -9
//	return 0;
//}



#include <string.h>
//int main()
//{
//	char arr[] = "abcdef";
//	int len = strlen(arr);  // strlen其实统计的是\0之前的字符个数
//	printf("%zd\n", len);
//	return 0;
//}

////写一个函数，求字符串长度   size_t是无符号整型
//size_t my_strlen(char* p)
//{
//	size_t count = 0; // 计数器
//	while (*p != '\0')
//	{
//		count++;
//		p++;  // 指针往后一步
//	}
//	return count;
//}
//
//size_t my_strlen(char* p)
//{
//	char* start = p;
//	char* end = p;
//	while (*end != '\0')
//	{
//		end++;  // 指针往后一步
//	}
//	return end-start;  // 就得到了指针和指针之间的元素个数
//}
//
//size_t my_strlen(char* p)
//{
//	char* start = p;
//	char* end = p;
//	while (*end )  // \0的ASCII值是0  0为假就不在循环
//	{
//		end++;  // 指针往后一步
//	}
//	return end - start;  // 就得到了指针和指针之间的元素个数
//}
//
//int main()
//{
//	char arr[] = "abcdef";
//	int len = my_strlen(arr);   // 数组名其实是数组首元素的地址 arr == &arr[0]
//	printf("%zd\n", len);
//	return 0;
//}

// 指针+指针？ 无意义

// 指针的关系运算
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };  // 下标增长 地址由低地址往高地址
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	int* p = &arr[0];
//	while (p < &arr[sz])  // &arr[sz-1] 是最后一个元素的地址 所以<&arr[sz]
//	{
//		printf("%d ", *p);
//		p++;
//	}
//	return 0;
//}

//概念： 野指针就是指针指向的位置是不可知的（随机的、不正确的、没有明确限制的）

// 指针未初始化
//int main()
//{
//	// 一个局部变量不初始化 ，它的值是随机的
//	int* p;  // p是局部变量，但是没有初始化，其值是随机值 ，如果将p中存放的值当做地址
//	// 解引用操作符就会形成非法访问
//	*p = 20;  //使用了未初始化的局部变量“p”
//	return 0;
//}

//int main()
//{
//	int arr[10] = { 0 };
//	int i = 0;
//	int* p = &arr[0];
//	for (i = 0; i <= 10; i++)
//	{
//		*p = i; //p在前面十次循环都是正常  但是11次循环时候 就会越界 变成野指针
//		p++;
//	}
//	return 0;
//}

//int* test()
//{
//	int n = 100;
//	return &n;
//}
//int main()
//{
//	int* p = test(); // n是局部变量
//	printf("%d\n", *p);
//	return 0;
//}

//6.2 如何规避野指针
//6.2.1 指针初始化
//如果明确知道指针指向哪⾥就直接赋值地址，如果不知道指针应该指向哪⾥，可以给指针赋值NULL.
//NULL 是C语⾔中定义的⼀个标识符常量，值是0，0也是地址，这个地址是⽆法使⽤的，读写该地址
//会报错。 NULL -- 空指针
//int main()
//{
//	int a = 10;
//	int* p = &a; // 给一个明确的地址
//
//	int* p2 = NULL; // 赋一个空指针
//	*p2 = 200; // 程序会引发异常 空指针不能用的
//}



//6.2.2 ⼩⼼指针越界
//⼀个程序向内存申请了哪些空间，通过指针也就只能访问哪些空间，不能超出范围访问，超出了就是
//越界访问。



//6.2.3 指针变量不再使⽤时，及时置NULL，指针使⽤之前检查有效性
//当指针变量指向⼀块区域的时候，我们可以通过指针访问该区域，后期不再使⽤这个指针访问空间的
//时候，我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是：只要是NULL指针就不去访问，
//同时使⽤指针之前可以判断指针是否为NULL。
//我们可以把野指针想象成野狗，野狗放任不管是⾮常危险的，所以我们可以找⼀棵树把野狗拴起来，
//就相对安全了，给指针变量及时赋值为NULL，其实就类似把野狗栓前来，就是把野指针暂时管理起
//来。
//不过野狗即使拴起来我们也要绕着⾛，不能去挑逗野狗，有点危险；对于指针也是，在使⽤之前，我
//们也要判断是否为NULL，看看是不是被拴起来起来的野狗，如果是不能直接使⽤，如果不是我们再去
//使⽤。

//int main()
//{
//	int* p = NULL;
//	if (p != NULL)
//	{
//		*p = 20;
//	}
//	return 0;
//}

//6.2.4 避免返回局部变量的地址


//assert断⾔
//assert.h 头⽂件定义了宏 assert() ，⽤于在运⾏时确保程序符合指定条件，如果不符合，就报
//错终⽌运⾏。这个宏常常被称为“断⾔”。
//上⾯代码在程序运⾏到这⼀⾏语句时，验证变量 p 是否等于 NULL 。如果确实不等于 NULL ，程序
//继续运⾏，否则就会终⽌运⾏，并且给出报错信息提⽰。

//#include <assert.h>
//int main()
//{
//	int* p = NULL;
//	assert(p != NULL);
//	return 0;
//}

//assert() 宏接受⼀个表达式作为参数。如果该表达式为真（返回值⾮零）， assert() 不会产⽣
//任何作⽤，程序继续运⾏。如果该表达式为假（返回值为零）， assert() 就会报错，在标准错误
//流 stderr 中写⼊⼀条错误信息，显⽰没有通过的表达式，以及包含这个表达式的⽂件名和⾏号。
//assert() 的使⽤对程序员是⾮常友好的，使⽤ assert() 有⼏个好处：它不仅能⾃动标识⽂件和
//出问题的⾏号，还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问
//题，不需要再做断⾔，就在 #include <assert.h> 语句的前⾯，定义⼀个宏 NDEBUG 。
//然后，重新编译程序，编译器就会禁⽤⽂件中所有的 assert() 语句。如果程序⼜出现问题，可以移
//除这条 #define NDBUG 指令（或者把它注释掉），再次编译，这样就重新启⽤了 assert() 语
//句。
//assert() 的缺点是，因为引⼊了额外的检查，增加了程序的运⾏时间。
//⼀般我们可以在 Debug 中使⽤，在 Release 版本中选择禁⽤ assert 就⾏，在 VS 这样的集成开
//发环境中，在 Release 版本中，直接就是优化掉了。这样在debug版本写有利于程序员排查问题，
//在 Release 版本不影响⽤⼾使⽤时程序的效率。

#include <assert.h>

// 求字符串长度 
// 参数s指向的字符串不期望被修改 加个const
//size_t my_strlen(const char* s)
//{
//	size_t count = 0;
//	assert(s != NULL);  //检测指针s是否有效 不能为空指针 
//	while (*s)  // \0 ASCII值为0
//	{
//		count++;
//		s++;
//	}
//	return count;
//}
//int main()
//{
//	char arr[] = "abcdef";
//	size_t len = my_strlen(arr);
//	printf("%zd\n", len);
//	return 0;
//}

//传值调⽤和传址调⽤
//学习指针的⽬的是使⽤指针解决问题，那什么问题，⾮指针不可呢？
//例如：写⼀个函数，交换两个整型变量的值
//void Swap1(int x, int y)
//{
//	int z = 0;
//	z = x;
//	x = y;
//	y = z;
//}
//int main()
//{
//	int a = 0;
//	int b = 0;
//	scanf("%d %d", &a, &b);
//	// 交换a和b的值
//	printf("交换前a=%d b=%d", a, b);
//	Swap1(a, b);//传值调用
//	printf("交换后a=%d b=%d", a, b);  // 发现并没有交换成功 是因为a b和x y地址不一样 虽然交换了x y（形参）
//	//但是实参a b并没有交换  当实参传递给形参的时候，形参是实参的一份临时拷贝 对形参的修改不会影响实参
//	return 0;
//}

// 那我就传地址
//void Swap1(int *pa, int *pb)
//{
//	int z = 0;
//	z = *pa; // z=a
//	*pa = *pb;//a=b
//	*pb = z;//b=z
//
//}
//int main()
//{
//	int a = 0;
//	int b = 0;
//	scanf("%d %d", &a, &b);
//	// 交换a和b的值
//	printf("交换前a=%d b=%d\n", a, b);
//	Swap1(&a,&b);//传址调用
//	printf("交换后a=%d b=%d\n", a, b);  
//	return 0;
//}

//结论：实参传递给形参的时候，形参会单独创建⼀份临时空间来接收实参，对形参的修改不影响实
//参。

//传址调⽤，可以让函数和主调函数之间建⽴真正的联系，在函数内部可以修改主调函数中的变量；所
//以未来函数中只是需要主调函数中的变量值来实现计算，就可以采⽤传值调⽤。如果函数内部要修改
//主调函数中的变量的值，就需要传址调⽤。

// 传值调用即可
int Add(int x, int y)
{
	int z = x + y;
	return z;
}
int main()
{
	int a = 10;
	int b = 20;
	int c = Add(a, b);
	printf("a+b = %d", c);
	return 0;
}